package com.rain.jwt.util;

import com.alibaba.fastjson.JSON;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.rain.jwt.common.BizException;
import com.rain.jwt.dto.PayloadDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.util.UUID;

import static com.rain.jwt.common.ResultEnum.*;

/**
 * @author: wu
 * @date: 2021/9/25
 */
public class JwtUtil {
    private static final Logger log = LoggerFactory.getLogger(JwtUtil.class);

    private static final String BEARER_TYPE = "Bearer";
    private static final String PARAM_TOKEN = "token";
    /**
     * 秘钥
     */
    private static final String SECRET = "dfg#fh!Fdh3443";
    /**
     * 有效期12小时
     */
    private static final long EXPIRE_TIME = 12 * 3600 * 1000;
    /**
     * 刷新时间7天
     */
    private static final long REFRESH_TIME = 7 * 24 * 3600 * 1000;

    /**
     * 生成token
     * @author wufeng
     * @date 2021/9/26 8:51
     * @param payloadDTO
     * @return {@link String}
     */
    public static String generate(PayloadDTO payloadDTO)  {
        //创建JWS头，设置签名算法和类型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256)
                .type(JOSEObjectType.JWT)
                .build();
        //将负载信息封装到Payload中
        Payload payload = new Payload(JSON.toJSONString(payloadDTO));
        //创建JWS对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        try {
            //创建HMAC签名器
            JWSSigner jwsSigner = new MACSigner(payloadDTO.getUserId() + SECRET+payloadDTO.getJti());
            //签名
            jwsObject.sign(jwsSigner);
            return jwsObject.serialize();
        } catch (JOSEException e) {
            log.error("jwt生成器异常",e);
            throw new BizException(TOKEN_SIGNER);
        }
    }

    /**
     * 刷新token
     * @author wufeng
     * @date 2021/9/26 8:51
     * @param token
     * @return {@link String}
     */
    public static String freshToken(String token)   {
        PayloadDTO payloadDTO;
        try {
            //从token中解析JWS对象
            JWSObject jwsObject = JWSObject.parse(token);
            payloadDTO = JSON.parseObject(jwsObject.getPayload().toString(), PayloadDTO.class);
            // 校验格式是否合适
            verifyFormat(payloadDTO, jwsObject);
        }catch (ParseException e) {
            log.error("jwt解析异常",e);
            throw new BizException(TOKEN_PARSE);
        } catch (JOSEException e) {
            log.error("jwt生成器异常",e);
            throw new BizException(TOKEN_SIGNER);
        }
        // 校验是否过期,未过期直接返回原token
        if (payloadDTO.getExp() >= System.currentTimeMillis()) {
            return token;
        }
        // 校验是否处于刷新时间内,重新生成token
        if (payloadDTO.getRef() >= System.currentTimeMillis()) {
            getRefreshPayload(payloadDTO);
            return generate(payloadDTO);
        }
        throw new BizException(TOKEN_EXP);
    }


    /**
     * 校验token格式是否合法
     *
     * @param payloadDTO
     * @param jwsObject
     * @return
     * @author wufeng
     * @date 2021/9/26 8:50
     */
    private static void verifyFormat(PayloadDTO payloadDTO, JWSObject jwsObject) throws JOSEException {
        //创建HMAC验证器
        JWSVerifier jwsVerifier = new MACVerifier(payloadDTO.getUserId() + SECRET+payloadDTO.getJti());
        if (!jwsObject.verify(jwsVerifier)) {
            throw new BizException(TOKEN_ERROR);
        }
    }

    /**
     * 从header获取token
     * @author wu
     * @date 2021/9/26 21:44
     * @param request
     * @return {@link String}
     */
    public static String getTokenFromHeader(HttpServletRequest request) {
        // 先从header取值
        String value = request.getHeader("Authorization");
        if (!StringUtils.hasText(value)) {
            // header不存在从参数中获取
            value = request.getParameter(PARAM_TOKEN);
            if (!StringUtils.hasText(value)) {
                throw new BizException(TOKEN_MUST);
            }
        }
        if (value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) {
            return value.substring(BEARER_TYPE.length()).trim();
        }
        return value;
    }

    /**
     * 校验token
     * @author wufeng
     * @date 2021/9/26 8:55
     * @param token
     * @return {@link PayloadDTO}
     */
    public static PayloadDTO verify(String token)  {
        PayloadDTO payloadDTO;
        try {
            //从token中解析JWS对象
            JWSObject jwsObject = JWSObject.parse(token);
            payloadDTO = JSON.parseObject(jwsObject.getPayload().toString(), PayloadDTO.class);
            // 校验格式是否合适
            verifyFormat(payloadDTO, jwsObject);
        }catch (ParseException e) {
            log.error("jwt解析异常",e);
            throw new BizException(TOKEN_PARSE);
        } catch (JOSEException e) {
            log.error("jwt生成器异常",e);
            throw new BizException(TOKEN_SIGNER);
        }
        // 校验是否过期
        if (payloadDTO.getExp() < System.currentTimeMillis()) {
            // 校验是否处于刷新时间内
            if (payloadDTO.getRef() >= System.currentTimeMillis()) {
                throw new BizException(TOKEN_REFRESH);
            }
            throw new BizException(TOKEN_EXP);
        }
        return payloadDTO;
    }

    /**
     * 生成默认的荷载
     * @author wufeng
     * @date 2021/9/26 8:51
     * @param userId
     * @return {@link PayloadDTO}
     */
    public static PayloadDTO getDefaultPayload(Long userId) {
        long currentTimeMillis = System.currentTimeMillis();
        PayloadDTO payloadDTO = new PayloadDTO();
        payloadDTO.setJti(UUID.randomUUID().toString());
        payloadDTO.setExp(currentTimeMillis + EXPIRE_TIME);
        payloadDTO.setRef(currentTimeMillis + REFRESH_TIME);
        payloadDTO.setUserId(userId);
        return payloadDTO;

    }

    /**
     * 刷新token，更新荷载
     * @author wufeng
     * @date 2021/9/26 8:51
     * @param payload
     * @return
     */
    public static void getRefreshPayload(PayloadDTO payload) {
        long currentTimeMillis = System.currentTimeMillis();
        payload.setJti(UUID.randomUUID().toString());
        payload.setExp(currentTimeMillis + EXPIRE_TIME);
        payload.setRef(currentTimeMillis + REFRESH_TIME);
    }
}
